
package kuti.grp5;

import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import static java.lang.Math.PI;
import java.util.Iterator;
import java.util.List;
import kuti.*;

/**
 *
 * The action manager manages the action for the kuti.
 * 
 * It checks if a action is valid, if all requests are valid, it does the action
 * (for example : moves, rotates, update radar, ...)
 * 
 * 
 * @author Marc Schorderet <marc.schorderet@edu.hefr.ch>
 * @author Vincenzo Do <vincenzo.do@edu.hefr.ch>
 * 
 */

public class ActionManager {
    
    /**
     * The current kuti
     */
    
    private Kuti kuti;
    
    /**
     * The simulation parameters
     */
    
    private ISimulParams simulParams;
    
    /**
     * The current collision manager
     */
    
    private CollisionManager collisionManager;
    
    /**
     * The current energy manager
     */
    
    private EnergyManager energyManager;
    
    /**
     * The constructor creates the action manager for one kuti
     * 
     * @param kuti          The current kuti
     * @param simulParams   The simulation parameters
     * 
     */
    
    public ActionManager(Kuti kuti, ISimulParams simulParams){
        
        // Check if the kuti is valid
        if(kuti == null){
            throw new IllegalArgumentException("The kuti is not defined.");
        }
        
        // Check if the simulation is valid
        if(simulParams == null){
            throw new IllegalArgumentException("The simulation parameters are not defined.");
        }
        
        // Ckeck if the collision manager is valid
        if(kuti.getCollisionManager() == null){
            throw new IllegalArgumentException("The collision manager is not valid.");
        }
        
        // Ckeck if the energy manager is valid
        if(kuti.getEnergyManager() == null){
            throw new IllegalArgumentException("The energy manager is not valid.");
        }
        
        this.kuti = kuti;
        this.simulParams = simulParams;
        this.collisionManager = kuti.getCollisionManager();
        this.energyManager = kuti.getEnergyManager();
        
    }
    
    /**
     * This method executes all authorised action for the current step.
     * 
     * @param stepNr    The current step number
     * @param observer  The world observer list
     * 
     */
    
    public void doActionForThisStep(long stepNr, List<IWorldObserver> observer){
        
        // Checks if the stepNr is valid
        if(stepNr < 0.0){
            throw new IllegalArgumentException("The step number can't be negatif");
        }
        
        // Check if the observer list is valid
        if(observer == null){
            throw new IllegalArgumentException("The observer list is not defined.");
        }
        
        // Get the actuator
        Actuator actuator = (Actuator) kuti.getActuator();
        
        // If the kuti is colliding
        if(kuti.getCollisionManager().isExternalCollision(stepNr)){
            
            // Set colliding state (movement)
            colliding();
            
            // Set colliding state (actuator)
            actuator.resetUpdate();
            
            return;
        }
        
        // check if the actions are valid
        checkAction();
        
        // Update the cutter state
        if(actuator.isCutterStateUpdated()){
            
            if(actuator.getCutterState() == ECutterState.ON){
                enableCutter();
            }else{
                disableCutter();
            }
            
        }
        
        // Update the orientation
        if(actuator.isRotateUpdated()){
            
            rotate(actuator.getOrientation());
            
        }
        
        // Update the moving state
        if(actuator.isMovingUpdated()){
            
            if(actuator.getMovingState() == EKutiState.MOVING){
                move(observer, stepNr);
            }else{
                stop();
            }
            
        }
        
        // Update the radar
        if(actuator.isRadarUpdated()){
            
            updateRadar(stepNr);
            
        }
        
        // Reset the actuator updates
        actuator.resetUpdate();
        
        // Update the energy manager
        energyManager.updateEnergyInfo();
        
        
    }
    
    /**
     * This method checks if the driver want execute illegal movements.
     * 
     * The following actions are authorised:
     * 
     * - If the kuti is stopped:
     *   - remain stopped, take the blades on or off
     *   - start, take the blades on or off
     * 
     * - If the kuti is mouving:
     *   - remain mouved, take the blades on or off
     *   - use radar, change orientation, take the blades on or off
     * 
     * /!\ All other instructions are not authorised and generate an 
     *     IllegalArgumentException!
     * 
     */
    
    private void checkAction(){
        
        // Get the actuator of the kuti
        Actuator a = (Actuator) kuti.getActuator();
        
        // If the kuti is mouving
        if(kuti.getState() == EKutiState.MOVING){
            
            // The kuti move and it use the radar
            if(a.isRadarUpdated()){
                throw new IllegalArgumentException("The radar can not be used in motion");
            }
                
            // The kuti move and it turn
            if(a.isRotateUpdated()){
                throw new IllegalArgumentException("The kuti can not turn in motion");
            }   
            
            // If the kuti is stopped
        }else if(kuti.getState() == EKutiState.STOPPED){
            
            if(a.getMovingState() == EKutiState.STOPPED){
                
                // It can do everyfing
                
            }else if(a.getMovingState() == EKutiState.MOVING){
                
                // The kuti move and it use the radar
                if(a.isRadarUpdated()){
                    throw new IllegalArgumentException("The radar can not be used in motion");
                }

                // The kuti move and it turn
                if(a.isRotateUpdated()){
                    throw new IllegalArgumentException("The kuti can not turn in motion");
                }
                
            }else{
                throw new IllegalArgumentException("The kuti state is not supported.");
            }
            
            
        }else{
            throw new IllegalArgumentException("The kuti state is not supported.");
        }
        
    }
    
    /**
     * This method does all actions to stop the kuti.
     * 
     */
    
    public void stop(){
        
        kuti.setState(EKutiState.STOPPED);
        
        kuti.setSpeed(0.0);
        
        Actuator a = (Actuator) kuti.getActuator();
        a.setTraveledDistance(0.0);
        kuti.setTraveledDistance(0.0);
        
        if(kuti.getCutterState() == ECutterState.ON){
            kuti.getEnergyManager().removeCutterEnergy();
        }
        
    }
    
    /**
     * This method does all actions to move the kuti.
     * 
     * @param observer  The world observer list
     * @param stepNr    The current step number
     * 
     */
    
    public void move(List<IWorldObserver> observer, long stepNr){
        
        // Checks if the stepNr is valid
        if(stepNr < 0.0){
            throw new IllegalArgumentException("The step number can't be negatif");
        }
        
        // Check if the observer list is valid
        if(observer == null){
            throw new IllegalArgumentException("The observer list is not defined.");
        }
        
        if(!kuti.getEnergyManager().hasEnergyToMove())
            return;
        
        kuti.setState(EKutiState.MOVING);
        
        double speed = simulParams.getKSpeed(kuti.getCutterState() == ECutterState.ON);
        kuti.setSpeed(speed);
        
        kuti.getEnergyManager().removeMovingEnergy();
        if(kuti.getCutterState() == ECutterState.ON){
            kuti.getEnergyManager().removeCutterEnergy();
        }
        
        moving(observer, stepNr);
        
    }
    
    /**
     * This method does all action to rotate the kuti.
     * 
     * @param rotation  Angle in radian of the angle.
     * 
     * The rotation angle must be a value between -Pi and +Pi.
     * 
     */
    
    public void rotate(double rotation){
        
        if(Double.isNaN(rotation) || rotation < -PI || rotation > PI){
            throw new IllegalArgumentException("The rotation angle is not valid. " +
                    "It must be a value between " + -PI + " and " + PI + ". [current value : " + rotation + "]" );
        }
        
        if(!kuti.getEnergyManager().hasEnergyToRotate())
            return;
        
        kuti.setOrientation(rotation);
        
        kuti.getEnergyManager().removeRotationEnergy();
        
    }
    
    /**
     * This method does all actions to enable cutters.
     * 
     */
    
    public void enableCutter(){
        
        kuti.setCutterState(ECutterState.ON);
        
    }
    
    /**
     * This method does all actions to disable cutters.
     * 
     */
    
    public void disableCutter(){
        
        kuti.setCutterState(ECutterState.OFF);
        
    }
    
    /**
     * This method does all actions after a colliding.
     * 
     */
    
    public void colliding(){
        
        disableCutter();
        stop();
        
        Actuator a = ((Actuator) kuti.getActuator());
        a.setColision(true);
        a.setMovingState(EKutiState.STOPPED);
        a.setCutterState(ECutterState.OFF);
        
    }
    
    /**
     * This method does all actions to update the radar.
     * 
     * @param stepNr    The current step number
     * 
     */
    
    public void updateRadar(long stepNr){
        
        // Checks if the stepNr is valid
        if(stepNr < 0.0){
            throw new IllegalArgumentException("The step number can't be negatif");
        }
        
        if(!kuti.getEnergyManager().hasEnergyToUseRadar())
            return;
        
        Radar radar = (Radar) kuti.getRadar();
        radar.updateRadar(kuti, kuti.getWorldManager(), stepNr);
        
    }
    
    /**
     * This method mouving the kuti and manage the collision with the collision
     * manager.
     * 
     * @param observer      The list of world observer
     * @param stepNr        The current step number
     */
    
    private void moving(List<IWorldObserver> observer, long stepNr) {
        
        // Checks if the stepNr is valid
        if(stepNr < 0.0){
            throw new IllegalArgumentException("The step number can't be negatif");
        }
        
        // Check if the observer list is valid
        if(observer == null){
            throw new IllegalArgumentException("The observer list is not defined.");
        }
        
        // Get the center of the kuti
        Point2D center = kuti.getCenterPosition();

        // Get kuti parameters
        double speed = kuti.getSpeed();
        double diameter = simulParams.getKDiameter();

        // Create kuti trajectory
        KutiTrajectory kt = new KutiTrajectory(kuti, speed, diameter);

        // Object colliding with
        IWorldElement collidingElement = null;
        
        CollisionList cl = new CollisionList();

        // collision distance
        double collisionDistance = Double.MAX_VALUE;

        // Check the collision with obstacles
        for (IObstacle io : kuti.getWorldManager().getAllObstacle()) {

            // Check if the kuti trajectory intersects with the obstacle
            if (kt.intersectsLine(io.segment())) {
                
                // calculate the intersection point to the collision
                Line2D intersectionPoint = kt.getIntersectionPoint(io.segment());

                // calculate the distance to the collision
                collisionDistance = intersectionPoint.getP1().distance(center);
                
                // Update collision element
                collidingElement = io;
                
                cl.addNewCollision(collidingElement, intersectionPoint.getP2(), collisionDistance);
                
            }
        }

        // Check collision with other kutis
        for (IKuti ik : kuti.getWorldManager().getAllKuti()) {
            
            if (ik != kuti) {
                
                if (kt.intersectsKuti(ik)) {

                    // calculate the intersection point to the collision
                    Line2D intersectionPoint = kt.getIntersectionPoint(ik);

                    // calculate the distance to the collision
                    collisionDistance = intersectionPoint.getP1().distance(kuti.getCenterPosition());

                    // Update collision element
                    collidingElement = ik;
                    
                    cl.addNewCollision(collidingElement, intersectionPoint.getP2(), collisionDistance);
                    
                }
                
            }
            
        }
        
        if (!cl.hasACollision()) {
            
            // Moving the kuti
            kuti.setCenterPosition(KutiTrajectory.moveTo(center, speed, kuti.getOrientation()));
            
            Actuator a = (Actuator) kuti.getActuator();
            a.setTraveledDistance(speed);
            kuti.setTraveledDistance(speed);
            
        } else {

            colliding();
            
            // Moving the kuti to the impact
            kuti.setCenterPosition(kt.getFrontCenter(collisionDistance));
            
            Actuator a = (Actuator) kuti.getActuator();
            a.setTraveledDistance(collisionDistance);
            kuti.setTraveledDistance(collisionDistance);
            
            Iterator it = cl.getCollisions().keySet().iterator();
            while(it.hasNext()){
                
                IWorldElement iwe = (IWorldElement) it.next();
                Point2D colPt = cl.getCollisions().get(iwe);
                
                kuti.getCollisionManager().setCollisionPoint(iwe, colPt, observer, stepNr);
                
            }

        }

    }
    
    
}
